import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

abstract class TagItem {
  String get tag;

  bool focusable = true;

  TagItem(this.focusable);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is TagItem && runtimeType == other.runtimeType;

  @override
  int get hashCode => 0;
}

class StringTagItem implements TagItem {
  final String string;

  StringTagItem(this.string, [this.focusable = true]);

  @override
  String get tag => string;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is StringTagItem &&
          runtimeType == other.runtimeType &&
          string == other.string;

  @override
  int get hashCode => string.hashCode;

  @override
  bool focusable;
}

extension StringTag on String {
  StringTagItem asTagItem([bool focusable = true]) =>
      StringTagItem(this, focusable);
}

typedef void OnTagSelectionChanged(dynamic selectedTagOrTags);

class SelectTagWidget<T extends TagItem> extends StatefulWidget {
  final bool singleSelection;
  final List<T> tags;
  final dynamic initialSelectedTags;
  final OnTagSelectionChanged onTagSelectionChanged;

  final Color backgroundColor;
  final Color selectedBorderColor;
  final Color unselectedBorderColor;
  final Color? selectedBackgroundColor;
  final Color unselectedBackgroundColor;
  final TextStyle textStyle;
  final TextStyle unselectedTextStyle;

  final double spacing;
  final double runSpacing;
  final EdgeInsetsGeometry padding;

  final bool? beautiful;
  final bool? fillSelected;
  final int? columnCount;

  final double mainAxisSpacing;
  final double crossAxisSpacing;
  final double height;
  final double borderRadius;

  final bool horizontal;
  final bool unselectedHasOutline;

  final bool updateState;

  const SelectTagWidget({
    Key? key,
    required this.tags,
    required this.onTagSelectionChanged,
    this.initialSelectedTags,
    this.singleSelection = true,
    this.backgroundColor = Colors.white,
    this.selectedBorderColor = const Color(0xffFA8C16),
    this.unselectedBorderColor = Colors.transparent,
    this.selectedBackgroundColor,
    this.unselectedBackgroundColor = const Color(0xffF7F7F7),
    this.textStyle = const TextStyle(fontSize: 15, color: Color(0xffFA8C16)),
    this.unselectedTextStyle =
        const TextStyle(fontSize: 15, color: Color(0xff252525)),
    this.spacing = 12.0,
    this.runSpacing = 8.0,
    this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
    this.beautiful,
    this.fillSelected,
    this.columnCount,
    this.mainAxisSpacing = 16.0,
    this.crossAxisSpacing = 16.0,
    this.height = 33.0,
    this.borderRadius = 4.0,
    this.horizontal = false,
    this.unselectedHasOutline = false,
    this.updateState = true,
  }) : super(key: key);

  @override
  _SelectTagWidgetState createState() => _SelectTagWidgetState();
}

class _SelectTagWidgetState<T extends TagItem> extends State<SelectTagWidget> {
  final List<T?> _selectedItems = [];

  @override
  void initState() {
    super.initState();
    if (widget.singleSelection == true) {
      if (widget.initialSelectedTags != null) {
        _selectedItems.add(widget.initialSelectedTags);
      }
    } else {
      widget.initialSelectedTags?.forEach((element) {
        _selectedItems.add(element);
      });
    }
  }

  double getItemWidth(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;
    return (screenWidth - (widget.columnCount! + 1) * widget.crossAxisSpacing) /
        widget.columnCount!;
  }

  @override
  Widget build(BuildContext context) {
    if (widget.horizontal)
      return ListView(
        scrollDirection: Axis.horizontal,
        shrinkWrap: true,
        padding: EdgeInsets.only(left: 16, top: 8, bottom: 8),
        children: <Widget>[
          for (final tag in widget.tags) ...[
            if (_selected(tag as T))
              buildSelectedWidget(tag)
            else
              buildUnselectedWidget(tag),
            SizedBox(width: 8),
          ],
        ],
      );
    return Container(
      color: widget.backgroundColor,
      padding: widget.padding,
      width: double.infinity,
      child: widget.columnCount == null
          ? Wrap(
              spacing: widget.spacing,
              runSpacing: widget.runSpacing,
              children: [
                for (final tag in widget.tags)
                  if (_selected(tag as T))
                    buildSelectedWidget(tag)
                  else
                    buildUnselectedWidget(tag)
              ],
            )
          : GridView.count(
              crossAxisCount: widget.columnCount!,
              shrinkWrap: true,
              physics: NeverScrollableScrollPhysics(),
              padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 4.0),
              crossAxisSpacing: widget.crossAxisSpacing,
              mainAxisSpacing: widget.mainAxisSpacing,
              childAspectRatio: getItemWidth(context) / widget.height,
              children: <Widget>[
                for (final tag in widget.tags)
                  if (_selected(tag as T))
                    buildSelectedWidget(tag)
                  else
                    buildUnselectedWidget(tag)
              ],
            ),
    );
  }

  Widget buildUnselectedWidget(T tag) {
    if (widget.beautiful != true) {
      if (widget.unselectedHasOutline == false)
        return SizedBox(
          height: widget.height,
          child: TextButton(
            style: TextButton.styleFrom(
              primary: widget.unselectedBackgroundColor,
              shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(widget.borderRadius)),
            ),
            child: Text(
              tag.tag,
              style: widget.unselectedTextStyle,
              key: ValueKey(tag.tag),
            ),
            onPressed: () {
              _toggle(tag);
            },
          ),
        );
      return OutlinedButton(
        style: OutlinedButton.styleFrom(
          side: BorderSide(color: widget.unselectedBorderColor),
          primary: widget.unselectedBackgroundColor,
          shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(widget.borderRadius)),
        ),
        child: Text(
          tag.tag,
          style: widget.unselectedTextStyle,
          key: ValueKey(tag.tag),
        ),
        onPressed: () {
          _toggle(tag);
        },
      );
    }
    return InkWell(
      child: Container(
        padding: EdgeInsets.symmetric(
          horizontal: 16.0,
          vertical: 8.0,
        ),
        decoration: BoxDecoration(
          color: widget.unselectedBackgroundColor,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(20.0),
            topRight: Radius.circular(20.0),
            bottomRight: Radius.circular(20.0),
          ),
          border: Border.all(
            color: Colors.transparent,
            width: 2,
          ),
        ),
        child: Text(
          tag.tag,
          style: widget.unselectedTextStyle,
          key: ValueKey(tag.tag),
        ),
      ),
      onTap: () {
        _toggle(tag);
      },
    );
  }

  Widget buildSelectedWidget(T tag) {
    final theme = Theme.of(context);
    if (widget.beautiful == true) {
      return InkWell(
        child: Container(
          //color: widget.backgroundColor,
          padding: EdgeInsets.symmetric(
            horizontal: 24.0,
            vertical: 8.0,
          ),
          decoration: BoxDecoration(
            color: widget.selectedBackgroundColor,
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(20.0),
              topRight: Radius.circular(20.0),
              bottomRight: Radius.circular(20.0),
            ),
            border: Border.all(
              color: widget.selectedBorderColor,
              width: 2,
            ),
          ),
          child: Text(
            tag.tag,
            style: widget.textStyle,
            key: ValueKey(tag.tag),
          ),
        ),
        onTap: () {
          _toggle(tag);
        },
      );
    } else if (widget.fillSelected == true) {
      return ElevatedButton(
        style: ElevatedButton.styleFrom(
          primary: widget.selectedBackgroundColor ?? theme.primaryColor,
          shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(widget.borderRadius)),
        ),
        child: Text(
          tag.tag,
          style: widget.textStyle,
          key: ValueKey(tag.tag),
        ),
        onPressed: () {
          _toggle(tag);
        },
      );
    } else {
      return OutlinedButton(
        style: OutlinedButton.styleFrom(
          side: BorderSide(color: theme.primaryColor),
          shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(widget.borderRadius)),
        ),
        child: Text(
          tag.tag,
          style: widget.textStyle,
          key: ValueKey(tag.tag),
        ),
        onPressed: () {
          _toggle(tag);
        },
      );
    }
  }

  bool _selected(T tag) => _selectedItems.contains(tag) && tag.focusable;

  void _toggle(T tag) {
    if (widget.singleSelection && widget.updateState == false) {
      widget.onTagSelectionChanged(tag);
      return;
    }
    setState(() {
      if (_selected(tag)) {
        _selectedItems.remove(tag);
        if (widget.singleSelection == true) {
          widget.onTagSelectionChanged(null);
        } else {
          widget.onTagSelectionChanged(_selectedItems);
        }
      } else {
        if (widget.singleSelection == true) {
          _selectedItems.clear();
          _selectedItems.add(tag);
          widget.onTagSelectionChanged(tag);
        } else {
          _selectedItems.add(tag);
          widget.onTagSelectionChanged(_selectedItems);
        }
      }
    });
  }
}
